package com.onecar.carplatform.jtt.codec;

import com.onecar.carplatform.common.protocol.annotations.JtMsgClassUtil;
import com.onecar.carplatform.common.protocol.basics.JtHeader;
import com.onecar.carplatform.common.protocol.basics.JtMessage;
import com.onecar.carplatform.common.utils.BcdUtil;
import com.onecar.carplatform.jtt.utils.JttUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import lombok.extern.slf4j.Slf4j;

import java.util.HashMap;
import java.util.List;

@Slf4j
public class JttMsgDecoder extends ByteToMessageDecoder {

    private static final HashMap<Integer, Class<? extends JtMessage>> msgMap;

    static {
        msgMap = JtMsgClassUtil.getClass("com.onecar.carplatform.common.protocol.jt808");
    }

    @Override
    protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
        log.debug("========> jtt get msg ip:{}, hex:{}", channelHandlerContext.channel().remoteAddress(), ByteBufUtil.hexDump(byteBuf));

        JtMessage msg = decode(byteBuf);
        if (msg != null) {
            list.add(msg);
        }
    }

    private JtMessage decode(ByteBuf byteBuf) {
        if (byteBuf.readableBytes() < 12) return null;
        byte[] buf = new byte[byteBuf.readableBytes()];
        byteBuf.readBytes(buf);
        ByteBuf rowMsg = antiEscape(buf);

        byte rowCheckSum = rowMsg.getByte(rowMsg.writerIndex() - 1);
        byte xorCheckCode = JttUtils.calculateXorCheckCode(rowMsg, rowMsg.readerIndex(), rowMsg.writerIndex() - 1);
        if (rowCheckSum != xorCheckCode) {
            log.warn("jtt message xor check code not correct, row check sum: {}, calculate check sum: {}", rowCheckSum, xorCheckCode);
            return null;
        }

        rowMsg.writerIndex(rowMsg.writerIndex() - 1);
        JtHeader jtHeader = parseHeader(rowMsg);
        if (jtHeader == null || jtHeader.getMsgBodyLen() != rowMsg.readableBytes()) {
            log.warn("jtt message parseHeader header is null or len is error");
            return null;
        }

        Class<? extends JtMessage> jtMessageClass = msgMap.get((int) jtHeader.getMsgId());
        if (jtMessageClass == null) {
            log.warn("msgMap has no class with message id {}", jtHeader.getMsgId());
            return null;
        }

        try {
            JtMessage message = jtMessageClass.getDeclaredConstructor().newInstance();
            message.setHeader(jtHeader);
            message.decodeMsgBody(rowMsg);
            log.debug("========> message parse success {}, toString: {}", jtHeader.getMsgId(), message);
            return message;
        } catch (Exception e) {
            log.error("mesId {} newInstance or decode failed, Exception:{}", jtHeader.getMsgId(), e.getMessage());
            return null;
        }
    }

    /**
     * @Author ljq
     * @Description 反转义协议，0x7d 0x01 -> 0x7d,  0x7d 0x02 -> 0x7e
     * @Date 20:09 2021/8/17
     * @Param [io.netty.buffer.ByteBuf]
     * @Return io.netty.buffer.ByteBuf
     **/
    private ByteBuf antiEscape(byte[] buf) {
        ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(buf.length);

        for (int i = 0; i < buf.length; i++) {
            if (buf[i] == 0x7d && buf[i + 1] == 0x01) {
                buffer.writeByte(0x7d);
                i++;
            } else if (buf[i] == 0x7d && buf[i + 1] == 0x02) {
                buffer.writeByte(0x7e);
                i++;
            } else {
                buffer.writeByte(buf[i]);
            }
        }

        return buffer;
    }

    /**
     * @Author ljq
     * @Description 解决 jtt 协议消息头
     * @Date 20:02 2021/8/17
     * @Param [io.netty.buffer.ByteBuf]
     * @Return com.onecar.carplatform.common.protocol.basics.JtHeader
     **/
    private JtHeader parseHeader(ByteBuf buf) {
        if (buf.readableBytes() < 15) {
            return null;
        }
        JtHeader header = new JtHeader();

        header.setMsgId(buf.readShort());
        short msgAttr = buf.readShort();
        header.setSubcontract(0 != (msgAttr & (0x01 << 13)));
        header.setEncryption((byte) ((msgAttr >> 10) & 0x07));
        header.setMsgBodyLen(msgAttr & 0x3ff);
        // 2019 之前的版本协议
        if (0 == (msgAttr & (0x01 << 14))) {
            header.setVersion((byte) 0);
            byte[] terminalPhone = new byte[6];
            buf.readBytes(terminalPhone);
            header.setTerminalPhone(BcdUtil.bcd2String(terminalPhone));
        } else {
            header.setVersion(buf.readByte());
            byte[] terminalPhone = new byte[10];
            buf.readBytes(terminalPhone);
            header.setTerminalPhone(BcdUtil.bcd2String(terminalPhone));
        }
        header.setMsgSerialNumber(buf.readShort());
        if (header.getSubcontract()) {
            header.setTotalPacket(buf.readShort());
            header.setPacketSequenceNum(buf.readShort());
        }

        return header;
    }
}
